/*********************************************************************
 * 源代码版权归作者（们）所有
 * <p>
 * 以 Apache License, Version 2.0 方式授权使用，具体参见：
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 ********************************************************************/
package com.skyworth.web.shiro;

import com.google.common.collect.Sets;
import com.skyworth.web.admin.role.domain.RoleModel;
import com.skyworth.web.common.Constants;
import com.skyworth.web.shiro.redis.RedisService;
import com.skyworth.web.admin.user.domain.UserModel;
import com.skyworth.web.admin.user.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Set;

/**
 * http://shiro.apache.org/java-authentication-guide.html
 * <p>
 * Subject Security specific user 'view' of an application user. It can be a
 * human being, a third-party process, a server connecting to you application
 * application, or even a cron job. Basically, it is anything or anyone
 * communicating with your application.
 * <p>
 * Principals A subjects identifying attributes. First name, last name, social
 * security number, username
 * <p>
 * Credentials secret data that are used to verify identities. Passwords,
 * Biometric data, x509 certificates,
 * <p>
 * Realms Security specific DAO, data access object, software component that
 * talkts to a backend data source. If you have usernames and password in LDAP,
 * then you would have an LDAP Realm that would communicate with LDAP. The idea
 * is that you would use a realm per back-end data source and Shiro would know
 * how to coordinate with these realms together to do what you have to do.
 *
 * @author
 */
public class ShiroRealmImpl extends AuthorizingRealm {

  static final Logger logger = LoggerFactory.getLogger(ShiroRealmImpl.class);

  @Autowired
  private UserService userService;

  @Autowired
  private RedisService redisService;


  private Set<String> addRolePermission(UserModel user){
    Set<String> subRoleSets = Sets.newLinkedHashSet();
    if(user.kind.ordinal()==2
            || user.kind.ordinal()==3){
      for(String key: Constants.ROLE_MAPPER.keySet()) {
        subRoleSets.add(Constants.ROLE_MAPPER.get(key));
      }
    }else {
      RoleModel roleModel = user.getRoleModel();
      String permssions = roleModel.getPermissionIds();
      //null as waiter
      if(permssions !=null) {
        String perm[] = roleModel.permissionIds.split(",");
        for (int i = 0; i < perm.length; i++) {
          subRoleSets.add(Constants.ROLE_MAPPER.get(perm[i]));
        }
      }
    }
    return subRoleSets;
  }
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(
      PrincipalCollection principals) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //获取当前登录的用户名
    UserModel user = (UserModel) getSessionAttribute(Constants.SESSION_USER_KEY);

    // 从principals中直接获取用户
    if (null == user) {
      List users = principals.asList();
      if (users.size() > 0 && users.get(0) instanceof  UserModel) {
        user = (UserModel) users.get(0);
      }
    }

    String key = Constants.AUTHORIZATIONINFO_PREFIX;

    if (null != user) {
      //判断是否已经缓存
      key = Constants.AUTHORIZATIONINFO_PREFIX + user.getUsername();
      if (redisService.exists(key)) {
        return (AuthorizationInfo) redisService.getObject(key);
      }
    }


    Set<String> roleSet = Sets.newLinkedHashSet(); // 角色集合
    Set<String> authoritySet = Sets.newLinkedHashSet(); // 权限集合
    if (user != null) {
      logger.info("加载个人权限-----------*************************------------>" + user.getUsername());

      //获取用户所有的权限
//      roleSet.add(String.valueOf(user.kind));
      if(user.kind.ordinal()==2){
        roleSet.add(Constants.OWNER_ROLE);
      }else if(user.kind.ordinal() ==3 ){
        roleSet.add(Constants.ADMIN_ROLE);
      }else {
        roleSet.add(Constants.WAITER_ROLE);
      }
    } else {
      throw new AuthorizationException();
    }
    //给当前用户设置角色
    info.addRoles(roleSet);
    //// TODO: 2016/9/22 for detail roles
    Set<String> subRoleSet = Sets.newLinkedHashSet(); // 角色集合
    subRoleSet = addRolePermission(user);
    info.addRoles(subRoleSet);
    //给当前用户设置权限
    info.addStringPermissions(authoritySet);
    //放入缓存
    redisService.set(key, info);
    return info;
  }

  /**
   * 认证回调函数,登录时调用.
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(
      AuthenticationToken authcToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

    UserModel user = userService.findUserByEmployeeNo(token.getUsername());
    if (user != null) {
      logger.info("-----------认证------------>" + user.getUsername());

//      if (user.kind == UserKind.STUDENT) {
//        logger.info("-----------学生不允许登陆------------>" + user.getUsername());
//
//        throw new AuthorizationException();
//      }

      this.setSession(Constants.SESSION_USER_KEY, user);
      return new SimpleAuthenticationInfo(user.getEmployeeNo(), user
          .getPassword(), getName());
    } else {
      logger.info("user "+token.getUsername() + " not found by employee no , try to find by name");
      user = userService.findUserByUsername(token.getUsername());
      if(null != user){
        this.setSession(Constants.SESSION_USER_KEY, user);
        return new SimpleAuthenticationInfo(user.getUsername(), user
                .getPassword(), getName());
      }else {
        logger.info("user "+token.getUsername() + "not found, try to find by phone");
        user = userService.findUserByMobile(token.getUsername());
        if(null != user){
          this.setSession(Constants.SESSION_USER_KEY, user);
          return new SimpleAuthenticationInfo(user.getMobile(), user
                  .getPassword(), getName());
        }else {
          logger.error("Authentication failed for user " + token.getUsername());
          throw new UnknownAccountException();//没找到帐号
        }
      }
    }
  }

  /**
   * ShiroSession设置
   * 使用时直接用HttpSession.getAttribute(key)就可以取到
   *
   * @see
   */
  private void setSession(Object key, Object value) {
    Subject currentUser = SecurityUtils.getSubject();
    if (null != currentUser) {
      Session session = currentUser.getSession();
      if (null != session) {
        session.setAttribute(key, value);
      }
    }
  }

  public static Object getSessionAttribute(String key) {
    Subject currentUser = SecurityUtils.getSubject();
    Session session = currentUser.getSession();
    return session.getAttribute(key);
  }

  /**
   * 重写退出时缓存处理方法
   */
  protected void doClearCache(PrincipalCollection principalcollection) {
    Object principal = principalcollection.getPrimaryPrincipal();
    redisService.del(Constants.AUTHORIZATIONINFO_PREFIX + principal.toString());
    redisService.del(Constants.SESSION_PREFIX + principal.toString());
    logger.debug(new StringBuffer().append(principal).append(" on logout to remove the cache [").append(principal)
        .append("]").toString());
  }
}
